#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

#include <FLAC/stream_decoder.h>

#include "../au.h"
#include "../common.h"
#include "afile.h"

enum {
	A_NULL,
	A_SIZE,
	A_RATE,
	A_CHAN,
	A_ENC
};

struct afile {
	FLAC__StreamDecoder	*dec;
	void			*buf;
	uint64_t		 sample;
	size_t			 icount;
	size_t			 size;
	size_t			 index;
	int			 first;
	int			 again;
	int			 fd;
};

static FLAC__StreamDecoderReadStatus fdcb_read(
    const FLAC__StreamDecoder *, FLAC__byte[], size_t *, void *);
static FLAC__StreamDecoderSeekStatus fdcb_seek(
    const FLAC__StreamDecoder *, FLAC__uint64, void *);
static FLAC__StreamDecoderTellStatus fdcb_tell(
    const FLAC__StreamDecoder *, FLAC__uint64 *, void *);
static FLAC__StreamDecoderLengthStatus fdcb_length(
    const FLAC__StreamDecoder *, FLAC__uint64 *, void *);
static FLAC__bool fdcb_eof(const FLAC__StreamDecoder *, void *);
static FLAC__StreamDecoderWriteStatus fdcb_write(
    const FLAC__StreamDecoder *, const FLAC__Frame *,
    const FLAC__int32 * const [], void *);
static void fdcb_meta(const FLAC__StreamDecoder *,
    const FLAC__StreamMetadata *, void *);
static void fdcb_error(const FLAC__StreamDecoder *,
    FLAC__StreamDecoderErrorStatus, void *);

const struct a_lookup stab[] = {
	{ "size", A_SIZE },
	{ "enc", A_ENC },
	{ "encoding", A_ENC },
	{ "chan", A_CHAN },
	{ "channel", A_CHAN },
	{ "channels", A_CHAN },
	{ "rate", A_RATE },
	{ NULL, A_NULL }
};

struct afile *
af_open(const char *file) {
	int fd;

	if (file == NULL)
		ERR(EINVAL, NULL);
	if ((fd = open(file, O_RDONLY)) < 0)
		return (NULL);
	return (af_fdopen(fd));
}

struct afile *
af_fdopen(int fd)
{
	struct afile *a;

	if (fd < 0)
		ERR(EBADF, NULL);
	if ((a = malloc(sizeof(struct afile))) == NULL)
		return (NULL);
	a->sample = 0;
	a->buf = NULL;
	a->icount = 0;
	a->size = 0;
	a->index = 0;
	a->fd = fd;
	if ((a->dec = FLAC__stream_decoder_new()) == NULL) {
		free(a);
		return (NULL);
	}
	if (FLAC__stream_decoder_init_stream(a->dec, &fdcb_read,
	    &fdcb_seek, &fdcb_tell, &fdcb_length, &fdcb_eof,
	    &fdcb_write, &fdcb_meta, &fdcb_error, a) !=
	    FLAC__STREAM_DECODER_INIT_STATUS_OK) {
		FLAC__stream_decoder_delete(a->dec);
		free(a);
		return (NULL);
	}
	if (!FLAC__stream_decoder_process_until_end_of_metadata(
	    a->dec)) {
		af_close(a);
		return (NULL);
	}
	FLAC__stream_decoder_process_single(a->dec);
	switch (FLAC__stream_decoder_get_state(a->dec)) {
	case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
		break;
	case FLAC__STREAM_DECODER_READ_METADATA:
		break;
	case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
		break;
	case FLAC__STREAM_DECODER_READ_FRAME:
		break;
	case FLAC__STREAM_DECODER_END_OF_STREAM:
		break;
	case FLAC__STREAM_DECODER_OGG_ERROR:
		/* FALLTHORUGH */
	case FLAC__STREAM_DECODER_SEEK_ERROR:
		/* FALLTHORUGH */
	case FLAC__STREAM_DECODER_ABORTED:
		/* FALLTHORUGH */
	case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
		/* FALLTHORUGH */
	case FLAC__STREAM_DECODER_UNINITIALIZED:
		af_close(a);
		return (NULL);
		break;
	}
	return (a);
}

void
af_close(struct afile *a)
{

	if (a == NULL)
		return;
	FLAC__stream_decoder_finish(a->dec);
	FLAC__stream_decoder_delete(a->dec);
	if (a->buf != NULL)
		free(a->buf);
	free(a);
}

ssize_t
af_read(struct afile *a, void *b, size_t s)
{
	size_t ssize;
	size_t i = 0;
	size_t n;

	if (a == NULL || b == NULL)
		ERR(EINVAL, -1);
	if (s == 0)
		return (0);
		if ((ssize = (FLAC__stream_decoder_get_bits_per_sample(
		    a->dec) + 7) / 8 *
		    FLAC__stream_decoder_get_channels(a->dec)) == 0)
			ERR(EINVAL, -1);
	s -= s % ssize;
	for (;;) {
		if (a->index < a->icount) {
			n = MIN(s - i, a->icount - a->index);
			(void)memcpy((char *)b + i, (char *)a->buf +
			    a->index, n);
			a->index += n;
			if ((i += n) == s)
				break;
		}
		a->index = a->icount = a->again = 0;
		FLAC__stream_decoder_process_single(a->dec);
		switch (FLAC__stream_decoder_get_state(a->dec)) {
		case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
			break;
		case FLAC__STREAM_DECODER_READ_METADATA:
			break;
		case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
			break;
		case FLAC__STREAM_DECODER_READ_FRAME:
			break;
		case FLAC__STREAM_DECODER_END_OF_STREAM:
			goto ret;
		case FLAC__STREAM_DECODER_OGG_ERROR:
			return (-1);
		case FLAC__STREAM_DECODER_SEEK_ERROR:
			return (-1);
		case FLAC__STREAM_DECODER_ABORTED:
			return (-1);
		case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
			return (-1);
		case FLAC__STREAM_DECODER_UNINITIALIZED:
			return (-1);
		}
		if (a->again) {
			if (i == 0)
				return (-1);
			else
				break;
		}
	}
ret:
	if (i > 0)
		a->sample += i / ssize;
	return (i);
}

int
af_get(struct afile *a, const char *s, ...)
{
	va_list ap;
	uint64_t u64;
	ssize_t r;
	unsigned u;
	unsigned o;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_SIZE:
			u64 = FLAC__stream_decoder_get_total_samples(
			    a->dec);
			u = FLAC__stream_decoder_get_channels(a->dec);
			o = (FLAC__stream_decoder_get_bits_per_sample(
			    a->dec) + 7) / 8;
			if (u64 == 0 || u == 0 || o == 0 ||
			    u64 > UINT32_MAX / u / o)
				*va_arg(ap, uint32_t *) = UINT32_MAX;
			else
				*va_arg(ap, uint32_t *) = u64 * u * o;
			break;
		case A_RATE:
			*va_arg(ap, uint32_t *) =
			    FLAC__stream_decoder_get_sample_rate
			    (a->dec);
			break;
		case A_CHAN:
			*va_arg(ap, uint32_t *) =
			    FLAC__stream_decoder_get_channels(a->dec);
			break;
		case A_ENC:
			if ((u =
			    FLAC__stream_decoder_get_bits_per_sample(
			    a->dec)) == 0)
				goto err;
			switch ((u - 1) / 8) {
			case 0:
				*va_arg(ap, uint32_t *) = AU_PCM8;
				break;
			case 1:
				*va_arg(ap, uint32_t *) = AU_PCM16;
				break;
			case 2:
				*va_arg(ap, uint32_t *) = AU_PCM24;
				break;
			case 3:
				*va_arg(ap, uint32_t *) = AU_PCM32;
				break;
			default:
				goto err;
			}
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

int
af_set(struct afile *a, const char *s, ...)
{

	ERR(EINVAL, -1);
}

struct timespec
af_seek(struct afile *a, struct timespec ts, int whence)
{
	const struct timespec tserr = { -1, -1 };
	unsigned rate;
	int negative;
	uint64_t c;
	uint64_t l;
	uint64_t t;

	rate = FLAC__stream_decoder_get_sample_rate(a->dec);
	negative = ts.tv_sec < 0 || (ts.tv_sec == 0 && ts.tv_nsec < 0);
	if (negative) {
		ts.tv_sec = -ts.tv_sec;
		ts.tv_nsec = -ts.tv_nsec;
	}
	if (ts.tv_nsec < 0) {
		ts.tv_sec--;
		ts.tv_nsec = 1000000000 - ts.tv_nsec;
	}
	t = (uint64_t)ts.tv_sec * (uint64_t)rate +
	    (uint64_t)ts.tv_nsec * (uint64_t)rate /
	    UINT64_C(1000000000);
	c = FLAC__stream_decoder_get_total_samples(a->dec);
	switch (whence) {
	case SEEK_CUR:
		l = a->sample;
		if (t == 0)
			goto ret;
		break;
	case SEEK_END:
		l = c;
		negative = !negative;
		break;
	case SEEK_SET:
		l = 0;
		break;
	default:
		ERR(EINVAL, tserr);
	}
	l = negative ? (t > l ? 0 : l - t) : l + t;
	if (l >= c)
		ERR(ERANGE, tserr);
	else if (!FLAC__stream_decoder_seek_absolute(a->dec, l))
		ERR(EOPNOTSUPP, tserr);
	a->icount = 0;
	a->sample = l;
ret:
	ts.tv_sec = l / (uint64_t)rate;
	ts.tv_nsec = UINT64_C(1000000000) * (l % (uint64_t)rate) /
	    (uint64_t)rate;
	return (ts);
}

struct timespec	
af_length(struct afile *a)
{
	struct timespec ts;
	uint64_t n;
	unsigned rate;

	n = FLAC__stream_decoder_get_total_samples(a->dec);
	rate = FLAC__stream_decoder_get_sample_rate(a->dec);
	ts.tv_sec = n / (uint64_t)rate;
	ts.tv_nsec = UINT64_C(1000000000) * (n % (uint64_t)rate) /
	    (uint64_t)rate;
	return (ts);
}

FLAC__StreamDecoderReadStatus
fdcb_read(const FLAC__StreamDecoder *dec, FLAC__byte b[], size_t *s,
    void *ctx)
{
	struct afile *a;
	size_t t;
	ssize_t r;

	assert(dec != NULL && b != NULL && s != NULL && ctx != NULL);
	a = ctx;
	t = *s;
	if ((r = read(a->fd, b, t)) < 0) {
		*s = 0;
		if (errno != EAGAIN)
			return (FLAC__STREAM_DECODER_READ_STATUS_ABORT);
		a->again = 1;
		return (FLAC__STREAM_DECODER_READ_STATUS_CONTINUE);
	}
	*s = r;
	return (r == 0 ?
	    FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM :
	    FLAC__STREAM_DECODER_READ_STATUS_CONTINUE);
}

FLAC__StreamDecoderSeekStatus
fdcb_seek(const FLAC__StreamDecoder *dec, FLAC__uint64 v, void *ctx)
{
	struct afile *a;

	assert(dec != NULL && ctx != NULL);
	a = ctx;
	if (lseek(a->fd, v, SEEK_SET) < 0)
		return (errno == ESPIPE ?
		    FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED :
		    FLAC__STREAM_DECODER_SEEK_STATUS_ERROR);
	return (FLAC__STREAM_DECODER_SEEK_STATUS_OK);
}

FLAC__StreamDecoderTellStatus
fdcb_tell(const FLAC__StreamDecoder *dec, FLAC__uint64 *v, void *ctx)
{
	struct afile *a;
	off_t r;

	assert(dec != NULL && v != NULL && ctx != NULL);
	a = ctx;
	if ((r = lseek(a->fd, 0, SEEK_CUR)) < 0)
		return (errno == ESPIPE ?
		    FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED :
		    FLAC__STREAM_DECODER_TELL_STATUS_ERROR);
	if (r > UINT64_MAX)
		ERR(EOVERFLOW, FLAC__STREAM_DECODER_TELL_STATUS_ERROR);
	*v = r;
	return (FLAC__STREAM_DECODER_TELL_STATUS_OK);
}

FLAC__StreamDecoderLengthStatus
fdcb_length(const FLAC__StreamDecoder *dec, FLAC__uint64 *v, void *ctx)
{
	struct afile *a;
	struct stat st;

	assert(dec != NULL && v != NULL && ctx != NULL);
	a = ctx;
	if (fstat(a->fd, &st) != 0)
		return (FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR);
	if (!S_ISREG(st.st_mode))
		return (FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED);
	if (st.st_size > UINT64_MAX)
		ERR(EOVERFLOW,
		    FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR);
	*v = st.st_size;
	return (FLAC__STREAM_DECODER_LENGTH_STATUS_OK);
}

FLAC__bool
fdcb_eof(const FLAC__StreamDecoder *dec, void *ctx)
{

	assert(dec != NULL && ctx != NULL);
	return (0);
}

FLAC__StreamDecoderWriteStatus
fdcb_write(const FLAC__StreamDecoder *dec, const FLAC__Frame *frm,
    const FLAC__int32 * const buf[], void *ctx)
{
	struct afile *a = ctx;
	uint8_t *b;
	void *p;
	size_t i;
	uint32_t u;
	int32_t v;
	unsigned t;
	unsigned f;
	int n;
	int c;

	/* This is probably unnecessary */
	for (c = 0; c < frm->header.channels; c++)
		if (buf[c] == NULL)
			goto err;
	f = frm->header.bits_per_sample;
	t = (f + 7) & ~7;
	i = frm->header.blocksize * frm->header.channels * (t / 8);
	if (i > a->size) {
		if ((p = (a->buf == NULL ? malloc(i) :
		    realloc(a->buf, i))) == NULL)
			goto err;
		a->buf = p;
		a->size = i;
	}
	a->icount = i;
	for(i = 0, b = a->buf; i < frm->header.blocksize; i++)
		for (c = 0; c < frm->header.channels; c++) {
			v = buf[c][i];
			if ((n = v < 0) != 0)
				v = -v;
			f = frm->header.bits_per_sample;
			while (f + f < t) {
				v |= (v << f);
				f += f;
			}
			if (f < t)
				v = (v << (t - f)) |
				    (v >> (f - (t - f)));
			u = n ? ~(uint32_t)v + UINT32_C(1) :
			    (uint32_t)v;
			for (f = t / 8; f > 0; f--)
				*b++ = (u >> (f - 1) * 8) &
				    UINT8_C(0xFF);
		}
	return (FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE);
err:
	return (FLAC__STREAM_DECODER_WRITE_STATUS_ABORT);
}

void
fdcb_meta(const FLAC__StreamDecoder *dec,
    const FLAC__StreamMetadata *meta, void *ctx)
{
	/* The information will be queried with the
	 * FLAC__stream_decoder_get_* functions.
	 */;
}

void
fdcb_error(const FLAC__StreamDecoder *dec,
    FLAC__StreamDecoderErrorStatus st, void *ctx)
{
	/* The state will be checked for errors. */;
}
